/*
 * Lincheck
 *
 * Copyright (C) 2019 - 2025 JetBrains s.r.o.
 *
 * This Source Code Form is subject to the terms of the
 * Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
 * with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

package org.jetbrains.lincheck.trace

import org.jetbrains.lincheck.util.isJavaLambdaClass

fun String.adornedClassNameRepresentation(): String = this
    .removeRefWrapperClassName()
    .getSimpleClassName()
    .removeJavaLambdaRuntimeAddress()
    .replaceNestedClassDollar()

/**
 * Replaces nested class dollars (if present) from string with dots.
 */
fun String.replaceNestedClassDollar(): String {
    val nestedClassRepresentation: String = this
    // If there's no dollar sign, return the original string
    if (!nestedClassRepresentation.contains('$')) return nestedClassRepresentation

    val result = StringBuilder()
    var currentIndex = 0

    while (currentIndex < nestedClassRepresentation.length) {
        // Find the next dollar sign
        val dollarIndex = nestedClassRepresentation.indexOf('$', currentIndex)
        // If no more dollar signs, append the rest and break
        if (dollarIndex == -1) {
            result.append(nestedClassRepresentation.substring(currentIndex))
            break
        }
        // Append the part before the dollar sign
        val before = nestedClassRepresentation.substring(currentIndex, dollarIndex)
        result.append(before)
        // Check if this dollar separates nested class names
        val afterDollarChar = nestedClassRepresentation[dollarIndex + 1]
        val isNestedClassNameSeparator = before.isNotEmpty() && before[0].isUpperCase() && afterDollarChar.isUpperCase()
        if (isNestedClassNameSeparator) {
            result.append('.')
        } else {
            result.append('$')
        }
        // Move past the dollar sign
        currentIndex = dollarIndex + 1
    }

    return result.toString()
}

/**
 * Removes java lambdas runtime address from classname.
 */
fun String.removeJavaLambdaRuntimeAddress(): String {
    if (isJavaLambdaClass(this)) return substringBeforeLast('/').substringBeforeLast('$')
    return this
}

/**
 * Removes outer class name for primitive types when they are wrapped with `Ref` class.
 * E.g. `kotlin.jvm.internal.Ref$IntRef@xxxxx` -> `IntRef@xxxxx`.
 *
 * *Note*: should be called on fully qualified class name.
 */
fun String.removeRefWrapperClassName(): String = removePrefix("kotlin.jvm.internal.Ref$")

/**
 * Returns simple class name - the last part of the fully qualified class name
 * (e.g. `java.lang.String` -> `String`).
 */
fun String.getSimpleClassName(): String = substringAfterLast(".")

// Trace polishing functions
fun String.hasCoroutinesCoreSuffix(): Boolean = endsWith("\$kotlinx_coroutines_core")

fun String.removeCoroutinesCoreSuffix(): String = removeSuffix("\$kotlinx_coroutines_core")

fun String.removeInlineIV(): String = removeSuffix("\$iv")

fun String?.isExactDollarThis(): Boolean = equals("\$this")

fun String.removeDollarThis(): String = if (isExactDollarThis()) this else removePrefix("\$this")

fun String.removeLeadingDollar(): String = removePrefix("$")

fun String.removeVolatileDollar(): String = removeSuffix("\$volatile")

fun String.removeVolatileDollarFU(): String = removeSuffix("\$volatile\$FU")

/**
 * Used fpr compressing default pairs.
 */
fun isDefaultPair(currentMethodName: String, nextMethodName: String): Boolean =
    currentMethodName == "${nextMethodName}\$default"

/**
 * Used fpr compressing access pairs.
 */
fun isAccessPair(currentMethodName: String, nextMethodName: String): Boolean =
    currentMethodName == "access$${nextMethodName}"

/**
 * Used for compressing synthetic accesses.
 */
fun isSyntheticFieldAccess(methodName: String): Boolean =
    isSyntheticGetFieldAccess(methodName) || isSyntheticSetFieldAccess(methodName)

fun isSyntheticGetFieldAccess(methodName: String): Boolean = methodName.contains("access\$get")

fun isSyntheticSetFieldAccess(methodName: String): Boolean = methodName.contains("access\$set")

/**
 * Used for compressing auto-generated field getters.
 */
fun isAutoGeneratedFieldGetterName(methodName: String, fieldName: String): Boolean =
    isConcreteAutoGeneratedFieldAccessName("get", methodName, fieldName)

/**
 * Used for compressing auto-generated field setters.
 */
fun isAutoGeneratedFieldSetterName(methodName: String, fieldName: String): Boolean =
    isConcreteAutoGeneratedFieldAccessName("set", methodName, fieldName)

private fun isConcreteAutoGeneratedFieldAccessName(methodPrefix: String, methodName: String, fieldName: String): Boolean {
    val fieldNameWithCapitalizedFirstLetter = fieldName.replaceFirstChar { it.uppercase() }
    return methodName == "$methodPrefix$fieldNameWithCapitalizedFirstLetter"
}